﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Claims;
using System.Text;

namespace OkTool.Util
{
    /// <summary>
    /// 反射工具类
    /// </summary>
    public class ReflectUtil
    {
        private static readonly ConcurrentDictionary<string, Delegate> DelegateCache = new();
        public static BindingFlags bf = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;

        /// <summary>
        /// 执行方法
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="methodName">方法名，区分大小写</param>
        /// <param name="args">方法参数</param>
        /// <typeparam name="T">约束返回的T必须是引用类型</typeparam>
        /// <returns>T类型</returns>
        public static T InvokeMethod<T>(object obj, string methodName, object[] args)
        {
            return (T)obj.GetType().GetMethod(methodName, args.Select(o => o.GetType()).ToArray()).Invoke(obj, args);
        }

        /// <summary>
        /// 执行方法
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="methodName">方法名，区分大小写</param>
        /// <param name="args">方法参数</param>
        /// <returns>T类型</returns>
        public static void InvokeMethod(object obj, string methodName, object[] args)
        {
            var type = obj.GetType();
            type.GetMethod(methodName, args.Select(o => o.GetType()).ToArray()).Invoke(obj, args);
        }

        /// <summary>
        /// 获得一个类中的所有构造列表
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<ConstructorInfo> GetConstructors(Type type)
        {
            return type.GetConstructors();
        }

        /// <summary>
        /// 获得指定类本类及其父类中的Public方法名
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static HashSet<string> GetPublicMethodNames(Type type)
        {
            HashSet<string> methodSet = new HashSet<string>();
            var methodArray = GetPublicMethods(type);
            if (ArrayUtil.IsNotEmpty(methodArray))
            {
                foreach (var item in methodArray)
                {
                    methodSet.Add(item.Name);
                }
            }
            return methodSet;
        }

        /// <summary>
        /// 获得指定类过滤后的Public方法列表
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<MethodInfo> GetPublicMethods(Type type)
        {
            return type == null ? null : type.GetMethods();
        }

        /// <summary>
        /// 获取指定特性
        /// </summary>
        /// <param name="type">可以是class,Method,Field</param>
        /// <param name="attributeType"></param>
        /// <returns></returns>
        public static System.Attribute GetAttribute(Type type, Type attributeType)
        {
            return type == null ? null : type.GetCustomAttribute(attributeType);
        }

        /// <summary>
        /// 获取特定特性的集合
        /// </summary>
        /// <param name="type">可以是class,Method,Field</param>
        /// <param name="attributeType"></param>
        /// <returns></returns>
        public static IEnumerable<System.Attribute> GetAttributes(Type type, Type attributeType)
        {
            return type == null ? null : type.GetCustomAttributes(attributeType);
        }

        /// <summary>
        /// 获得指定类包含某个特性的Public方法列表
        /// </summary>
        /// <param name="type">类</param>
        /// <param name="attributeType"></param>
        /// <returns></returns>
        public static List<MethodInfo> GetPublicMethodsByAttribute(Type type, Type attributeType)
        {
            return type.GetMethods()
                .Where(method => method.GetCustomAttributes(attributeType, true).Length > 0)
                .ToList();
        }
        /// <summary>
        /// 获得指定类过滤后的Public方法列表
        /// </summary>
        /// <param name="type"></param>
        /// <param name="filter">过滤器</param>
        /// <returns></returns>
        public static IEnumerable<MethodInfo> GetPublicMethods(Type type, Func<MethodInfo, bool> filter)
        {
            if (type == null)
            {
                return null;
            }
            var methods = GetPublicMethods(type);

            List<MethodInfo> methodList = null;
            if (null != filter)
            {
                methodList = new List<MethodInfo>();
                foreach (var method in methods)
                {
                    if (filter(method))
                    {
                        methodList.Add(method);
                    }
                }
            }
            else
            {
                methodList = methods.ToList();
            }
            return methodList;
        }
        /// <summary>
        /// 获得指定类过滤后的Public方法列表
        /// </summary>
        /// <param name="type">查找方法的类</param>
        /// <param name="excludeMethodNames">不包括的方法名列表</param>
        /// <returns></returns>
        public static IEnumerable<MethodInfo> GetPublicMethods(Type type, params string[] excludeMethodNames)
        {
            var excludeMethodSet = new HashSet<string>(excludeMethodNames);
            return GetPublicMethods(type, method => !excludeMethodSet.Contains(method.Name));
        }

        /// <summary>
        /// 获得指定类过滤后的Public方法列表
        /// </summary>
        /// <param name="type"></param>
        /// <param name="excludeMethods">不包括的方法</param>
        /// <returns></returns>
        public static IEnumerable<MethodInfo> GetPublicMethods(Type type, params MethodInfo[] excludeMethods)
        {
            var excludeMethodSet = excludeMethods.ToList();
            return GetPublicMethods(type, method => !excludeMethodSet.Contains(method));
        }


        /// <summary>
        /// 查找指定方法 如果找不到对应的方法则返回null
        /// </summary>
        /// <param name="type"></param>
        /// <param name="methodName"></param>
        ///  <param name="args">参数</param>
        /// <returns></returns>
        public static MethodInfo GetMethod(Type type, string methodName, params Type[] args)
        {
            if (type == null || StrUtil.IsBlank(methodName))
            {
                return null;
            }
            return type.GetMethod(methodName, args);
        }
        /// <summary>
        /// 获取所有的字段信息
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <returns>字段信息</returns>
        public static FieldInfo[] GetFields(object obj)
        {
            FieldInfo[] fieldInfos = obj.GetType().GetFields(bf);
            return fieldInfos;
        }

        /// <summary>
        /// 获取字段
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="name">字段名</param>
        /// <typeparam name="T">约束返回的T必须是引用类型</typeparam>
        /// <returns>T类型</returns>
        public static T GetField<T>(object obj, string name)
        {
            return GetProperty<T>(obj, name);
        }

        /// <summary>
        /// 获取属性
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="name">属性名</param>
        /// <typeparam name="T">约束返回的T必须是引用类型</typeparam>
        /// <returns>T类型</returns>
        public static T GetProperty<T>(object obj, string name)
        {
            return (T)GetProperty(obj, name);
        }

        /// <summary>
        /// 获取属性
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="name">属性名</param>
        /// <returns>T类型</returns>
        public static object GetProperty(object obj, string name)
        {
            var type = obj.GetType();
            if (DelegateCache.TryGetValue(type.Name + "." + name, out var func))
            {
                return func.DynamicInvoke(obj);
            }
            var parameter = Expression.Parameter(type, "e");
            var property = Expression.PropertyOrField(parameter, name);
            func = Expression.Lambda(property, parameter).Compile();
            DelegateCache.TryAdd(type.Name + "." + name, func);
            return func.DynamicInvoke(obj);
        }

        /// <summary>
        /// 设置字段
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="name">字段名</param>
        /// <param name="value">值</param>
        public static void SetField<T>(T obj, string name, object value) where T : class
        {
            SetProperty(obj, name, value);
        }

        /// <summary>
        /// 设置属性
        /// </summary>
        /// <param name="obj">反射对象</param>
        /// <param name="name">属性名</param>
        /// <param name="value">值</param>
        /// <returns>旧值</returns>
        public static void SetProperty<T>(T obj, string name, object value) where T : class
        {
            var type = obj.GetType();
            var parameter = Expression.Parameter(type, "e");
            var property = Expression.PropertyOrField(parameter, name);
            var before = Expression.Lambda(property, parameter).Compile().DynamicInvoke(obj);
            if (value == before)
            {
                return ;
            }

            if (property.Type.IsGenericType && property.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                if (value is IConvertible)
                {
                    var v = System.Convert.ChangeType(value,property.Type.GenericTypeArguments[0]);

                    type.GetProperty(name)?.SetValue(obj, v);
                }
                else
                {
                    type.GetProperty(name)?.SetValue(obj, value);
                }
            }
            else
            {
                var valueExpression = Expression.Parameter(property.Type, "v");
                var assign = Expression.Assign(property, valueExpression);
                if (value is IConvertible)
                {
                    var v = System.Convert.ChangeType(value, property.Type);

                    Expression.Lambda(assign, parameter, valueExpression).Compile().DynamicInvoke(obj, v);
                }
                else
                {
                    Expression.Lambda(assign, parameter, valueExpression).Compile().DynamicInvoke(obj, value);
                }
            }

        }
    }
}
